package com.kamistoat.netty.netty.simple;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;

import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;

/**
 * Netty服务端的自定义Handler，需要继承Netty规定好的某个HandlerAdapter这样该Handler才能被用于Netty
 * 直接继承 ChannelInboundHandlerAdapter, 然后重写它预定义的方法即可。
 */
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    /**
     * 当 channel为读就绪状态时触发该事件方法。
     * 该方法会将当前整个Socket封装为 ChannelHandlerContext, 并将channel中的数据封装，一并作为该方法的输入参数
     * 对于服务端而言，就是Client发送消息
     * @param ctx ChannelHandler的上下文对象。含有: 管道pipeline 通道channel
     * @param msg channel中处于读就绪的数据
     * @throws Exception
     */
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("Server: " + Thread.currentThread().getName());
        // 将msg转成 ByteBuf。注意区别 Netty的ByteBuf 和 JavaNIO中的 ByteBuffer
        // ByteBuf的性能更高
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println("Client发送信息: " + byteBuf.toString(StandardCharsets.UTF_8));
        System.out.println("Client地址: " + ctx.channel().remoteAddress());

        // TaskQueue: 假如出现了一个非常耗时的业务，可以将其提交到当前NIOSocketChannel所在的NIOEventLoop的TaskQueue中异步执行
        // 如果不做任何处理，那么在休眠的10秒内，仍然是一个阻塞的，因此可以将其提交到当前NIOEventLoop的TaskQueue中
        // cex.channel() 获取当前SocketChannel，.eventLoop() 获取当前NIOEventLoop
        ctx.channel().eventLoop().execute(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(10 * 1000);
                    ctx.writeAndFlush(Unpooled.copiedBuffer("刚睡醒~", StandardCharsets.UTF_8));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
    }

    /**
     * 当channel由读就绪变为空闲时，触发该事件方法。
     * 可以认为数据以读取完毕，用于执行收尾工作，例如服务端读取客户端发送数据后，向客户端回复数据
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        // ctx.write() 将数据写到缓冲区
        // ctx.writeAndFlush() 将数据写到缓冲区并刷新到pipeline中
        // 这两个方法都需要对数据进行预编码
        ctx.writeAndFlush(Unpooled.copiedBuffer("hello, Client~", StandardCharsets.UTF_8));
    }

    /**
     * 当发生异常时触发该事件方法，通常使用该方法在异常时关闭通道
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}
